﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mls.Interpreters
{
    public class Parser : AbstractParser
    {
        #region Constants
        //private const byte DefaultPageNumberCharacter = (byte)'#';
        private const Language.Type DefaultLanguage = Language.Type.Latin;
        private const Smartee.RichText.Justification DefaultJustification = Smartee.RichText.Justification.Left;
        private readonly ColumnDefinition[] HeaderAndFooterLayout = new ColumnDefinition[] {
                    new ColumnDefinition() { Justify = Smartee.RichText.Justification.Left, Start = 10, End = 25 },
                    new ColumnDefinition() { Justify = Smartee.RichText.Justification.Center, Start = 26, End = 41 },
                    new ColumnDefinition() { Justify = Smartee.RichText.Justification.Right, Start = 42, End = 58 }
                };
        #endregion

        #region Main Controller
        private IMlsInterpreter _interpreter = null;

        // Parser Status Variables
        private bool _isInParagraph = false;
        private bool _isInEmbededCommands = false;
        private bool _isInTable = false;
        private bool _isInRow = false;
        private Language.Type _language = DefaultLanguage;
        private Smartee.RichText.Justification _justify = DefaultJustification;
        private ColumnDefinition[] _currentTable = null;
        private bool _isInHeader = false;
        private bool _isInFooter = false;
        private bool _isInFootnote = false;
        private Language.Type _languageBeforeFootnote = Language.Type.Latin;
        private bool _underline = false;
        private bool _strike = false;

        public Parser(IMlsInterpreter interpreter)
        {
            _interpreter = interpreter;
        }

        public override void Parse(Document document)
        {
            InitStateForParsing();

            _interpreter.StartDocument();
            base.Parse(document);
            EndCurrentParagraphOrTable();
            _interpreter.EndDocument();
        }

        public void Parse(DocumentsChain chain)
        {
            _interpreter.StartDocument();
            foreach (Document document in chain.Documents)
            {
                InitStateForParsing();
                base.Parse(document);
                EndCurrentParagraphOrTable();
            }
            _interpreter.EndDocument();
        }

        private void InitStateForParsing()
        {
            _isInParagraph = false;
            _isInEmbededCommands = false;
            _isInTable = false;
            _isInRow = false;
            _language = DefaultLanguage;
            _justify = DefaultJustification;
            _currentTable = null;
            _isInHeader = false;
            _isInFooter = false;
            _isInFootnote = false;
            _underline = false;
            _strike = false;
        }
        #endregion

        #region Table Hooks
        protected override void StartNewCell(Character[] text)
        {
            // In case this is the first row of a table
            if (!_isInTable)
            {
                // Table cannot start on a paragraph
                if (_isInParagraph)
                    throw new InvalidOperationException("Cannot start a new table from inside a paragraph");
                if (_isInRow)
                    throw new InvalidOperationException("Cannot start a new table while another one is already current");

                _interpreter.StartTable(_currentTable);
                _interpreter.StartRow();
                _interpreter.StartCell();
                _isInTable = true;
                _isInRow = true;
            }
            // If already in table row, just start new cell
            else
            {
                if (_isInParagraph)
                {
                    if (!_isInRow)
                        throw new InvalidOperationException("Cannot start a new cell while not in row");

                    EndCurrentParagraph();
                }

                // First cell in a row
                if (!_isInRow)
                {
                    _interpreter.StartRow();
                    _isInRow = true;
                }
                else
                    _interpreter.EndCell();

                _interpreter.StartCell();
            }
        }

        protected override void EndRow(Character[] text)
        {
            if (!_isInRow)
                throw new InvalidOperationException("Trying to end row while not in a row");

            if (_isInParagraph)
                EndCurrentParagraph();
            _interpreter.EndCell();
            _interpreter.EndRow();

            _isInRow = false;

            // Allow only one row for headers and footers
            if (_isInHeader || _isInFooter)
                EndCurrentTable();
        }
        #endregion

        #region Paragraph Hooks
        private Stack<Character> _text = new Stack<Character>();

        protected override void ParagraphSeparator(Character text)
        {
            if (_isInTable && !_isInRow)
                EndCurrentTable();

            if (!_isInParagraph)
            {
                _isInParagraph = true;
                _interpreter.StartParagraph(_justify, text.Language.LanguageType);
            }

            EndCurrentParagraph();
        }

        protected override void StartCommandBlock()
        {
            EndCurrentParagraphOrTable();
        }

        protected override void PageAdvanceCommand(Character[] commandText)
        {
            EndCurrentParagraphOrTable();

            _interpreter.InsertPageBreak();
        }

        protected override void ParagraphCharacter(Character character)
        {
            if (_isInTable && !_isInRow)
                EndCurrentTable();

            // If not the first character in the paragraphy, update flags as nescesarry
            if (_isInParagraph)
            {
                if (_language != character.Language.LanguageType)
                {
                    DispatchText();
                    _interpreter.ChangeLanguage(_language = character.Language.LanguageType);
                }

                bool underline = (character.Flags & Flags.UnderLine) == Flags.UnderLine;
                bool strike = (character.Flags & Flags.UnderLine) == Flags.Strike;

                if (_underline != underline)
                {
                    DispatchText();
                    _interpreter.Underline = _underline = underline;
                }
                if (_strike != strike)
                {
                    DispatchText();
                    _interpreter.Strike = _strike = strike;
                }
            }
            else
                StartParagraphIfNotAlreadyInOne(character);

            if (character.IsArabicDigit)
            {
                Stack<Character> digits = new Stack<Character>();
                while (_text.Count > 0 && _text.Peek().IsArabicDigit)
                    digits.Push(_text.Pop());
                _text.Push(character);
                while (digits.Count > 0)
                    _text.Push(digits.Pop());
            }
            else
                _text.Push(character);
        }

        protected override void PageNumber(Character character)
        {
            StartParagraphIfNotAlreadyInOne(character);
            _interpreter.InsertPageNumber();
        }

        #region Paragraph Start/End Helpers
        private void DispatchText()
        {
            if (_text.Count > 0)
            {
                string text = string.Empty;
                while (_text.Count > 0)
                    text = CharactersMapper.Map(_text.Pop()) + text;
                _interpreter.InsertText(text);
            }
        }

        private void StartParagraphIfNotAlreadyInOne(Character firstCharacter)
        {
            if (!_isInParagraph)
            {
                Smartee.RichText.Justification justify = Smartee.RichText.Justification.Nutral;
                if (!_isInTable)
                    justify = firstCharacter.Language.IsArabic && _justify == Smartee.RichText.Justification.Nutral ?
                        Smartee.RichText.Justification.Right : _justify;
                _interpreter.StartParagraph(justify, _language = firstCharacter.Language.LanguageType);
                _isInParagraph = true;
                _interpreter.Underline = _underline = (firstCharacter.Flags & Flags.UnderLine) == Flags.UnderLine;
                _interpreter.Strike = _strike = (firstCharacter.Flags & Flags.UnderLine) == Flags.Strike;
            }
        }

        private void EndCurrentParagraphOrTable()
        {
            if (_isInTable)
                EndCurrentTable();
            else if (_isInParagraph)
                EndCurrentParagraph();
        }

        private void EndCurrentParagraph()
        {
            if (!_isInParagraph)
                throw new InvalidOperationException("Trying to end paragraph while not in a paragraph");
            DispatchText();
            _interpreter.EndParagraph();
            _isInParagraph = false;
        }

        private void EndCurrentTable()
        {
            if (!_isInTable)
                throw new InvalidOperationException("Trying to end table while not in table");
            if (_isInRow)
                throw new InvalidOperationException("Trying to end table while in a not ended row");
            if (_isInParagraph)
                throw new InvalidOperationException("Trying to end table while in a paragraph");

            _interpreter.EndTable();
            _isInTable = false;

            if (_isInHeader)
            {
                _interpreter.EndHeader();
                _isInHeader = false;
            }
            else if (_isInFooter)
            {
                _interpreter.EndFooter();
                _isInFooter = false;
            }
        }
        #endregion
        #endregion

        #region Embeded Content
        protected override void FootnoteCommand(Character[] commandText)
        {
            DispatchText();
            _interpreter.StartFootnote();
            _isInFootnote = true;
            _isInEmbededCommands = false;
            _isInParagraph = false;
            _languageBeforeFootnote = _language;
        }

        protected override void HeaderCommand(Character[] commandText)
        {
            _interpreter.StartHeader();
            _isInHeader = true;
            _interpreter.StartTable(HeaderAndFooterLayout);
            _isInTable = true;
        }

        protected override void FooterCommand(Character[] commandText)
        {
            _interpreter.StartFooter();
            _isInFooter = true;
            _interpreter.StartTable(HeaderAndFooterLayout);
            _isInTable = true;
        }

        protected override void StartEmbed(Character character)
        {
            if (_isInEmbededCommands)
                throw new InvalidOperationException("Embeding doesn't support nesting");

            if (!_isInParagraph)
            {
                int i = 0;
                while (_document.ReadAhead(i).Type != Token.EndOfFile && _document.ReadAhead(i).Type != Token.Embed)
                    i++;

                StartParagraphIfNotAlreadyInOne(
                    _document.ReadAhead(i).Type != Token.EndOfFile && _document.ReadAhead(i + 1).Type != Token.EndOfFile ?
                    _document.ReadAhead(i + 1) : character);
            }

            _isInEmbededCommands = true;
        }

        protected override void EndEmbed(Character character)
        {
            if (_isInEmbededCommands)
                _isInEmbededCommands = false;
            else if (_isInFootnote)
            {
                EndCurrentParagraphOrTable();
                _interpreter.EndFootnote();
                _isInFootnote = false;
                _isInParagraph = true;
                _interpreter.ChangeLanguage(_language = _languageBeforeFootnote);
            }
        }
        #endregion

        #region Status Executive Commands
        protected override void JustifyCommand(Character[] commandText, Smartee.RichText.Justification justify)
        {
            _justify = justify;
        }

        protected override void BoldCommand(Character[] commandText, bool isBold)
        {
            _interpreter.BoldFace = isBold;
        }

        protected override void FontCommand(Character[] commandText, Language.Type language, int fontId)
        {
            _interpreter.SetFont(language, fontId);
        }

        protected override void TableCommand(Character[] commandText, ColumnDefinition[] definition)
        {
            _currentTable = definition;
        }

        protected override void PageNumberCommand(Character[] commandText, int newPageNumber)
        {
            //_interpreter.pa;
        }

        protected override void PageNumberCodeCommand(Character[] commandText)
        {
            // Do nothing
        }
        #endregion

        #region Errors Handling
        protected override void UnexpectedCharacters(Character[] text, string errorMessage)
        {
            foreach (Character character in text)
                ParagraphCharacter(character);
        }

        protected override void NotHandledCommand(Character[] commandText)
        {
            // Do nothing
        }

        protected override void InvalidCommandArguments(Character[] commandText)
        {
            // Do nothing
        }

        protected override void UnknownCommand(Character[] commandText)
        {
            // Do nothing
        }

        protected override void IntercommandsWhitespaces(Character[] whitespaces)
        {
            // Do nothing
        }
        #endregion
    }
}
